/*-------------------------------------------------------------------------
 *
 * ts_locale.c
 *		tsearch的区域设置兼容层
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/ts_locale.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/pg_collation.h"
#include "common/string.h"
#include "storage/fd.h"
#include "tsearch/ts_locale.h"
#include "tsearch/ts_public.h"

static void fc_tsearch_readline_callback(void *fc_arg);


/*
 * 这些函数使用 3-wchar_t 输出缓冲区的原因，而不是你可能预期的 2，是因为在 Windows 上 "wchar_t" 是 16 位的，而我们从 char2wchar() 获取的是 UTF16 而不是 UTF32。因此，单个输入字符可能产生一个替代对，而不仅仅是一个 wchar_t；我们还需要为结尾的空字符留出空间。当我们确实得到了替代对时，我们只将第一个代码传递给 iswdigit() 等，因此这些函数对于基础多语言平面之外的字符总是返回 false。
 */
#define WC_BUF_LEN  3

int t_isdigit(const char *fc_ptr)
{
	int			fc_clen = pg_mblen(fc_ptr);
	wchar_t		fc_character[WC_BUF_LEN];
	pg_locale_t fc_mylocale = 0;	/* TODO */

	if (fc_clen == 1 || database_ctype_is_c)
		return isdigit(TOUCHAR(fc_ptr));

	char2wchar(fc_character, WC_BUF_LEN, fc_ptr, fc_clen, fc_mylocale);

	return iswdigit((wint_t) fc_character[0]);
}

int t_isspace(const char *fc_ptr)
{
	int			fc_clen = pg_mblen(fc_ptr);
	wchar_t		fc_character[WC_BUF_LEN];
	pg_locale_t fc_mylocale = 0;	/* TODO */

	if (fc_clen == 1 || database_ctype_is_c)
		return isspace(TOUCHAR(fc_ptr));

	char2wchar(fc_character, WC_BUF_LEN, fc_ptr, fc_clen, fc_mylocale);

	return iswspace((wint_t) fc_character[0]);
}

int t_isalpha(const char *fc_ptr)
{
	int			fc_clen = pg_mblen(fc_ptr);
	wchar_t		fc_character[WC_BUF_LEN];
	pg_locale_t fc_mylocale = 0;	/* TODO */

	if (fc_clen == 1 || database_ctype_is_c)
		return isalpha(TOUCHAR(fc_ptr));

	char2wchar(fc_character, WC_BUF_LEN, fc_ptr, fc_clen, fc_mylocale);

	return iswalpha((wint_t) fc_character[0]);
}

int t_isprint(const char *fc_ptr)
{
	int			fc_clen = pg_mblen(fc_ptr);
	wchar_t		fc_character[WC_BUF_LEN];
	pg_locale_t fc_mylocale = 0;	/* TODO */

	if (fc_clen == 1 || database_ctype_is_c)
		return isprint(TOUCHAR(fc_ptr));

	char2wchar(fc_character, WC_BUF_LEN, fc_ptr, fc_clen, fc_mylocale);

	return iswprint((wint_t) fc_character[0]);
}


/*
 * 设置以使用 tsearch_readline() 读取文件。此功能比直接读取文件更好，因为它提供了指向检测到问题的特定行的错误上下文。
 *
 * 预期用法是：
 *
 *		tsearch_readline_state trst;
 *
 *		if (!tsearch_readline_begin(&trst, filename))
 *			ereport(ERROR,
 *					(errcode(ERRCODE_CONFIG_FILE_ERROR),
 *					 errmsg("无法打开停用词文件 \"%s\": %m",
 *							filename)));
 *		while ((line = tsearch_readline(&trst)) != NULL)
 *			process line;
 *		tsearch_readline_end(&trst);
 *
 * 请注意，调用者提供 ereport() 用于文件打开失败；这是为了提供自定义消息。传递给 tsearch_readline_begin() 的文件名字符串必须在 tsearch_readline_end() 期间保持有效。
 */
bool tsearch_readline_begin(tsearch_readline_state *fc_stp,
					   const char *fc_filename)
{
	if ((fc_stp->fp = AllocateFile(fc_filename, "r")) == NULL)
		return false;
	fc_stp->filename = fc_filename;
	fc_stp->lineno = 0;
	initStringInfo(&fc_stp->buf);
	fc_stp->curline = NULL;
	/* 为ereport()设置错误追踪支持 */
	fc_stp->cb.callback = fc_tsearch_readline_callback;
	fc_stp->cb.arg = (void *) fc_stp;
	fc_stp->cb.previous = error_context_stack;
	error_context_stack = &fc_stp->cb;
	return true;
}

/*
 * 从 tsearch 数据文件中读取下一行（预计为 UTF-8），如果需要，将其转换为数据库编码。返回的字符串是 palloc 的。
 * 返回 NULL 表示 EOF。
 */
char * tsearch_readline(tsearch_readline_state *fc_stp)
{
	char	   *fc_recoded;

	/* 增加用于错误报告的行号 */
	fc_stp->lineno++;

	/* 清除 curline，它不再相关 */
	if (fc_stp->curline)
	{
		if (fc_stp->curline != fc_stp->buf.data)
			pfree(fc_stp->curline);
		fc_stp->curline = NULL;
	}

	/* 收集下一行（如果有） */
	if (!pg_get_line_buf(fc_stp->fp, &fc_stp->buf))
		return NULL;

	/* 验证输入是否为 UTF-8，然后在需要时转换为数据库编码 */
	fc_recoded = pg_any_to_server(fc_stp->buf.data, fc_stp->buf.len, PG_UTF8);

	/* 保存正确编码的字符串以备可能的错误报告 */
	fc_stp->curline = fc_recoded;		/* 可能等于 buf.data */

	/*
	 * 我们总是返回一个新分配的 pstrdup 字符串。如果 pg_any_to_server() 返回 buf.data，这是显然必要的，即使发生了编码转换，我们也需要一个第二份副本。调用者有权在任何时候释放返回的字符串，这将使 curline 指向回收的存储，如果在那之后发生错误将导致问题。
	 * （返回 pstrdup 的结果而不是 pg_any_to_server 的输出是更可取的，因为转换结果往往被过度分配。由于调用者可能将结果字符串直接保存到一个长寿命的字典结构中，我们不希望它是比必要更大的 palloc 块。我们将在下一个调用时回收转换结果。）
	 */
	return pstrdup(fc_recoded);
}

/*
 * 在使用 tsearch_readline() 读取文件后关闭。
 */
void tsearch_readline_end(tsearch_readline_state *fc_stp)
{
	/* 抑制在下面报告的任何错误中使用 curline */
	if (fc_stp->curline)
	{
		if (fc_stp->curline != fc_stp->buf.data)
			pfree(fc_stp->curline);
		fc_stp->curline = NULL;
	}

	/* 释放其他资源 */
	pfree(fc_stp->buf.data);
	FreeFile(fc_stp->fp);

	/* 弹出错误上下文栈 */
	error_context_stack = fc_stp->cb.previous;
}

/*
 * 错误上下文回调，用于读取 tsearch
 * 配置文件时发生的错误。
 */
static void fc_tsearch_readline_callback(void *fc_arg)
{
	tsearch_readline_state *fc_stp = (tsearch_readline_state *) fc_arg;

	/*
	 * 我们无法包含 config 行的文本，以便处理
	 * 在 tsearch_readline() 本身期间发生的错误。
	 * 这些错误的主要原因是编码违规，我们不敢尝试
	 * 打印包含错误编码数据的错误消息。
	 */
	if (fc_stp->curline)
		errcontext("line %d of configuration file \"%s\": \"%s\"",
				   fc_stp->lineno,
				   fc_stp->filename,
				   fc_stp->curline);
	else
		errcontext("line %d of configuration file \"%s\"",
				   fc_stp->lineno,
				   fc_stp->filename);
}


/*
 * lowerstr --- 将以 null 结束的字符串转换为小写
 *
 * 返回的字符串是 palloc'd
 */
char * lowerstr(const char *fc_str)
{
	return lowerstr_with_len(fc_str, strlen(fc_str));
}

/*
 * lowerstr_with_len --- 将字符串转换为小写
 *
 * 输入字符串不需要以 null 结束。
 *
 * 返回的字符串是 palloc'd
 */
char * lowerstr_with_len(const char *fc_str, int fc_len)
{
	char	   *fc_out;
	pg_locale_t fc_mylocale = 0;	/* TODO */

	if (fc_len == 0)
		return pstrdup("");

	/*
	 * 仅在最大编码长度 > 1 且 ctype != C 时使用宽字符代码。
	 * 一些操作系统在使用多字节编码和 C 区域设置时会失败。
	 * 此外，对于 C 区域设置，没有必要将其作为多字节处理。从
	 * backend/utils/adt/oracle_compat.c Teodor
	 */
	if (pg_database_encoding_max_length() > 1 && !database_ctype_is_c)
	{
		wchar_t    *fc_wstr,
				   *fc_wptr;
		int			fc_wlen;

		/*
		 * 为最坏的情况分配 wchar_t 数量，len 包含
		 * 字节数 >= 字符数，并为 0 分配 1 wchar_t，因为
		 * wchar2char 需要以零结束的字符串
		 */
		fc_wptr = fc_wstr = (wchar_t *) palloc(sizeof(wchar_t) * (fc_len + 1));

		fc_wlen = char2wchar(fc_wstr, fc_len + 1, fc_str, fc_len, fc_mylocale);
		Assert(fc_wlen <= fc_len);

		while (*fc_wptr)
		{
			*fc_wptr = towlower((wint_t) *fc_wptr);
			fc_wptr++;
		}

		/*
		 * 为最坏情况 + '\0' 分配结果字符串
		 */
		fc_len = pg_database_encoding_max_length() * fc_wlen + 1;
		fc_out = (char *) palloc(fc_len);

		fc_wlen = wchar2char(fc_out, fc_wstr, fc_len, fc_mylocale);

		pfree(fc_wstr);

		if (fc_wlen < 0)
			ereport(ERROR,
					(errcode(ERRCODE_CHARACTER_NOT_IN_REPERTOIRE),
					 errmsg("conversion from wchar_t to server encoding failed: %m")));
		Assert(fc_wlen < fc_len);
	}
	else
	{
		const char *fc_ptr = fc_str;
		char	   *fc_outptr;

		fc_outptr = fc_out = (char *) palloc(sizeof(char) * (fc_len + 1));
		while ((fc_ptr - fc_str) < fc_len && *fc_ptr)
		{
			*fc_outptr++ = tolower(TOUCHAR(fc_ptr));
			fc_ptr++;
		}
		*fc_outptr = '\0';
	}

	return fc_out;
}
